﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Lidgren.Network;
using Lidgren.Network.Xna;
using Microsoft.Xna.Framework.Input;

namespace Projektarbete
{
    static class BasicNet
    {
        static NetClient client;
        static NetServer server;

        //NET-TODO:
        //static Player localPlayer;

        static Dictionary<long, string> playerNameDictionary;

        static bool isClient, isServer;

        static MapHandler localMap;

        enum RUServerMessageType
        {
            MapData = 0,
            InitialPlayerData = 1,
            PlayerDisconnected = 2
        }
        enum RSServerMessageType
        {
            PlayerPositionChange = 0
        }
        enum ROServerMessageType
        {
            TileInteracted = 0
        }
        enum RUClientMessageType
        {
            InitialPlayerData = 0
        }
        enum RSClientMessageType
        {
            InputUpdate = 0
        }
        enum ROClientMessageType
        {
        }

        //NET-TODO:
        /*static public void JoinServer(string IP, Player LocalPlayer, ref MapHandler LocalMap)
        {
            localPlayer = LocalPlayer;
            localMap = LocalMap;
            NetPeerConfiguration netConfig = new NetPeerConfiguration("Projektarbete");
            client = new NetClient(netConfig);
            client.Start();
            isClient = true;
            client.DiscoverLocalPeers(5557);
        }*/

        static public void HostServer(ref MapHandler Map)
        {
            NetPeerConfiguration netConfig = new NetPeerConfiguration("Projektarbete");
            netConfig.Port = 5557;
            netConfig.EnableMessageType(NetIncomingMessageType.DiscoveryRequest);
            server = new NetServer(netConfig);
            playerNameDictionary = new Dictionary<long, string>();
            server.Start();
            isServer = true;

            localMap = Map;
        }

        static public void Update(GameTime gameTime, byte frameCounter, Dictionary<string, NetPlayer> playerDict)
        {
            if (isClient)
            {
                NetIncomingMessage incomingMessage;
                while ((incomingMessage = client.ReadMessage()) != null)
                {
                    switch (incomingMessage.MessageType)
                    {
                        case NetIncomingMessageType.Data:
                            switch (incomingMessage.DeliveryMethod)
                            {
                                case NetDeliveryMethod.ReliableOrdered:
                                    switch ((ROServerMessageType)incomingMessage.SequenceChannel)
                                    {
                                        case ROServerMessageType.TileInteracted:
                                            Point interactedTileLocation = incomingMessage.ReadPoint();
                                            //NET-TODO
                                            //localMap.map[interactedTileLocation.Y][interactedTileLocation.X].Interact();
                                            break;
                                        default:
                                            throw new NotImplementedException();
                                    }
                                    break;
                                case NetDeliveryMethod.ReliableSequenced:
                                    switch ((RSServerMessageType)incomingMessage.SequenceChannel)
                                    {
                                        case RSServerMessageType.PlayerPositionChange:
                                            string playerNameKey = incomingMessage.ReadString().ToLower();
                                            Vector2 playerPosition = incomingMessage.ReadVector2();
                                            if (playerDict.ContainsKey(playerNameKey))
                                                playerDict[playerNameKey].position = playerPosition;
                                            break;
                                        default:
                                            throw new NotImplementedException();
                                    }
                                    break;
                                case NetDeliveryMethod.ReliableUnordered:
                                    switch ((RUServerMessageType)incomingMessage.ReadUInt16())
                                    {
                                        case RUServerMessageType.MapData:
                                            ushort mapSizeY = incomingMessage.ReadUInt16();
                                            ushort mapSizeX = incomingMessage.ReadUInt16();

                                            Tile[][] tempTileArray = new Tile[mapSizeY][];

                                            for (int y = 0; y < mapSizeY; y++)
                                            {
                                                tempTileArray[y] = new Tile[mapSizeX];
                                                for (int x = 0; x < mapSizeX; x++)
                                                {
                                                    //Net-TODO: send blocksVision. See Tile.cs
                                                    //tempTileArray[y][x] = new Tile(incomingMessage.ReadUInt16(), true, incomingMessage.ReadByte());
                                                    //Fix ^
                                                    ushort textureID = incomingMessage.ReadUInt16();
                                                    bool blocksVision = incomingMessage.ReadBoolean();
                                                    switch ((TileTypes)incomingMessage.ReadByte())
                                                    {
                                                        case TileTypes.DoorClosed:
                                                        case TileTypes.DoorOpen:
                                                            tempTileArray[y][x] = new Door(blocksVision);
                                                            break;
                                                        case TileTypes.Chest:
                                                            break;
                                                        default:
                                                            tempTileArray[y][x] = new Tile(textureID, blocksVision);
                                                            break;
                                                    }
                                                }
                                            }

                                            //NET-TODO
                                            //localMap.ChangeMap(tempTileArray);
                                            break;
                                        case RUServerMessageType.InitialPlayerData:
                                            string name = incomingMessage.ReadString();
                                            string nameKey = name.ToLower();
                                            Vector2 playerPosition = incomingMessage.ReadVector2();
                                            playerDict.Add(nameKey, new NetPlayer(name, 100, 2f, playerPosition, Global.Playersheet));
                                            break;
                                        case RUServerMessageType.PlayerDisconnected:
                                            string disconnectingPlayerName = incomingMessage.ReadString();
                                            playerDict.Remove(disconnectingPlayerName.ToLower());
                                            break;
                                        default:
                                            throw new NotImplementedException();
                                    }
                                    break;
                                default:
                                    throw new NotImplementedException();
                            }
                            break;
                        case NetIncomingMessageType.DiscoveryResponse:
                            client.Connect(incomingMessage.SenderEndpoint);
                            break;
                        case NetIncomingMessageType.StatusChanged:
                            switch (client.ServerConnection.Status)
                            {
                                //NET-TODO:
                                /*case NetConnectionStatus.Connected:
                                    NetOutgoingMessage outgoingMessage = client.CreateMessage();
                                    outgoingMessage.Write((byte)RUClientMessageType.InitialPlayerData); // This is an InitialPlayerData message, the server needs to know that!
                                    outgoingMessage.Write(localPlayer.getName); <-- ERROR DÄR (localPlayer)
                                    outgoingMessage.Write((Vector2)localPlayer.position); <-- ERROR DÄR (localPlayer)
                                    client.SendMessage(outgoingMessage, NetDeliveryMethod.ReliableUnordered);
                                    break;*/
                            }
                            break;
                        default:
                            break;
                    }
                }
            }
            else if (isServer)
            {
                NetIncomingMessage incomingMessage;
                while ((incomingMessage = server.ReadMessage()) != null)
                {
                    switch (incomingMessage.MessageType)
                    {
                        case NetIncomingMessageType.Data:
                            switch (incomingMessage.DeliveryMethod)
                            {
                                case NetDeliveryMethod.ReliableOrdered:
                                    break;
                                case NetDeliveryMethod.ReliableSequenced:
                                    /*switch ((RSClientMessageType)incomingMessage.SequenceChannel)
                                    {
                                        case RSClientMessageType.InputUpdate:
                                            long uniqueID = incomingMessage.SenderConnection.RemoteUniqueIdentifier;
                                            Vector2 playerPosition = incomingMessage.ReadVector2();
                                            int numberOfInteractedTiles = incomingMessage.ReadUInt16();
                                            for (int i = 0; i < numberOfInteractedTiles; i++)
                                            {
                                                Point derp = incomingMessage.ReadPoint();
                                                if (localMap.map[derp.Y][derp.X].propertyID == 2 || localMap.map[derp.Y][derp.X].propertyID == 3)
                                                {
                                                    localMap.map[derp.Y][derp.X].Interact();
                                                    NetOutgoingMessage outgoingInteractionMessage = server.CreateMessage();
                                                    outgoingInteractionMessage.Write(derp);
                                                    server.SendToAll(outgoingInteractionMessage, incomingMessage.SenderConnection, NetDeliveryMethod.ReliableOrdered, 0);
                                                }
                                            }
                                            if (playerNameDictionary.ContainsKey(uniqueID))
                                                playerDict[playerNameDictionary[uniqueID]].position = playerPosition;
                                            break;
                                        default:
                                            throw new NotImplementedException();
                                    }*/
                                    break;
                                case NetDeliveryMethod.ReliableUnordered:
                                    switch ((RUClientMessageType)incomingMessage.ReadByte())
                                    {
                                        case RUClientMessageType.InitialPlayerData:
                                            long uniqueIdentifier = incomingMessage.SenderConnection.RemoteUniqueIdentifier;
                                            string playerName = incomingMessage.ReadString();
                                            string nameKey = playerName.ToLower();
                                            Vector2 playerPosition = incomingMessage.ReadVector2();
                                            playerDict.Add(nameKey, new NetPlayer(playerName, 100, 2f, playerPosition, Global.Playersheet));
                                            playerDict[nameKey].IsPlaying = true;
                                            playerNameDictionary.Add(uniqueIdentifier, playerName.ToLower());
                                            NetOutgoingMessage outgoingInitialData = server.CreateMessage();
                                            outgoingInitialData.Write((ushort)RUServerMessageType.InitialPlayerData);
                                            outgoingInitialData.Write(playerDict[playerNameDictionary[uniqueIdentifier]].getName);
                                            outgoingInitialData.Write(playerDict[playerNameDictionary[uniqueIdentifier]].position);
                                            server.SendToAll(outgoingInitialData, incomingMessage.SenderConnection, NetDeliveryMethod.ReliableUnordered, 0);
                                            break;
                                        default:
                                            throw new NotImplementedException();
                                    }
                                    break;
                                default:
                                    throw new NotImplementedException();
                            }
                            break;
                        case NetIncomingMessageType.DiscoveryRequest:
                            NetOutgoingMessage outgoingMessage = server.CreateMessage();
                            outgoingMessage.Write("Server here, what's up?");
                            server.SendDiscoveryResponse(outgoingMessage, incomingMessage.SenderEndpoint);
                            break;
                        case NetIncomingMessageType.StatusChanged:
                            switch (incomingMessage.SenderConnection.Status)
                            {
                                case NetConnectionStatus.Connected:
                                    //BIG FAT NET-TODO
                                    //ushort mapSizeY = (ushort)localMap.map.GetLength(0);
                                    //ushort mapSizeX = (ushort)localMap.map[0].GetLength(0);

                                    NetOutgoingMessage outgoingMapData = server.CreateMessage();
                                    outgoingMapData.Write((ushort)RUServerMessageType.MapData);
                                    //outgoingMapData.Write(mapSizeY);
                                    //outgoingMapData.Write(mapSizeX);

                                    //for (int y = 0; y < mapSizeY; y++)
                                    //{
                                    //    for (int x = 0; x < mapSizeX; x++)
                                    //    {
                                    //        ushort textureID = localMap.map[y][x].getTextureID;
                                    //        outgoingMapData.Write(textureID);
                                    //        outgoingMapData.Write(localMap.map[y][x].BlocksVision);
                                    //        outgoingMapData.Write((byte)Collision.TileType(textureID));
                                    //        //outgoingMapData.Write((byte)localMap.map[y][x].propertyID);
                                    //    }
                                    //}
                                    //server.SendMessage(outgoingMapData, incomingMessage.SenderConnection, NetDeliveryMethod.ReliableUnordered);

                                    foreach (KeyValuePair<long, string> playerEntry in playerNameDictionary)
                                    {
                                        NetOutgoingMessage playerDataMessage = server.CreateMessage();
                                        playerDataMessage.Write((ushort)RUServerMessageType.InitialPlayerData);
                                        playerDataMessage.Write(playerDict[playerEntry.Value].getName);
                                        playerDataMessage.Write(playerDict[playerEntry.Value].position);
                                        server.SendMessage(playerDataMessage, incomingMessage.SenderConnection, NetDeliveryMethod.ReliableUnordered, 0);
                                    }
                                    break;
                                case NetConnectionStatus.Disconnected:
                                    NetOutgoingMessage playerDisconnectedMessage = server.CreateMessage();
                                    playerDisconnectedMessage.Write((ushort)RUServerMessageType.PlayerDisconnected);
                                    playerDisconnectedMessage.Write(playerDict[playerNameDictionary[incomingMessage.SenderConnection.RemoteUniqueIdentifier]].getName);
                                    server.SendToAll(playerDisconnectedMessage, incomingMessage.SenderConnection, NetDeliveryMethod.ReliableUnordered, 0);

                                    playerDict[playerNameDictionary[incomingMessage.SenderConnection.RemoteUniqueIdentifier]].IsPlaying = false;
                                    playerNameDictionary.Remove(incomingMessage.SenderConnection.RemoteUniqueIdentifier);
                                    break;
                                default:
                                    break;
                            }
                            break;
                        default:
                            break;
                    }
                }
            }
        }

        internal static void UpdateLocalMovement(Vector2 playerPosition, Point[] interactedTiles)
        {
            if (isClient)
            {
                NetOutgoingMessage outgoingPlayerInput = client.CreateMessage();
                outgoingPlayerInput.Write(playerPosition);
                outgoingPlayerInput.Write((ushort)interactedTiles.Length);
                foreach (Point interactedTilePosition in interactedTiles)
                {
                    outgoingPlayerInput.Write(interactedTilePosition);
                }
                client.SendMessage(outgoingPlayerInput, NetDeliveryMethod.ReliableSequenced, 0); // Sequencechannel 0 means player position data!
            }
        }

        internal static void UpdatePlayerPositions(Dictionary<string, NetPlayer> playerDict)
        {
            if (isServer)
            {
                foreach (KeyValuePair<long, string> playerEntry in playerNameDictionary)
                {
                    NetOutgoingMessage outgoingPositionMessage = server.CreateMessage();
                    outgoingPositionMessage.Write(playerDict[playerEntry.Value].getName);
                    outgoingPositionMessage.Write(playerDict[playerEntry.Value].position);
                    server.SendToAll(outgoingPositionMessage,
                        server.Connections.Find(delegate(NetConnection connection)
                        {
                            return connection.RemoteUniqueIdentifier == playerEntry.Key;
                        }),
                        NetDeliveryMethod.ReliableSequenced, 0);
                }
            }
        }

        internal static void UpdateDraw(Point tilePosition)
        {
            if (isClient)
            {

            }
            else if (isServer)
            {
            }
        }

        internal static void Disconnect()
        {
            if (isClient)
                client.Disconnect("Bye, server!");
            else if (isServer)
            {
                foreach (NetConnection connection in server.Connections)
                    connection.Disconnect("Server shutting down.");
            }
        }
    }
}
